package cn.mujiankeji.mbrowser.功能.广告拦截
import cn.mujiankeji.mbrowser.功能.广告拦截.项目.拦截地址信息
import cn.mujiankeji.mbrowser.功能.广告拦截.项目.广告拦截规则信息
import cn.mujiankeji.mbrowser.功能.广告拦截.项目.广告拦截订阅文件详情
import cn.mujiankeji.mbrowser.功能.广告拦截.项目.请求的网页信息
import cn.mujiankeji.mbrowser.工具.mlog
import cn.mujiankeji.mbrowser.工具.操作.简易文本匹配器
import cn.mujiankeji.mbrowser.数据.配置项.应用配置
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import 操作.工具类.left
import 操作.工具类.right
import 操作.工具类.right2
import 操作.工具类.文本操作
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write

/**
 * 广告拦截操作类
 * 
 * 负责广告规则文件的管理，包括订阅规则的存储、查询、更新与删除
 * 使用JSON文件替代数据库实现，更加轻量高效
 */
object 广告拦截操作 {


    // 常规规则集
    val 规则文件列表 : MutableList<广告拦截订阅文件详情> = ArrayList()
    private var 初始化完成 = false
    suspend fun 初始化(){
        withContext(Dispatchers.IO){
            读写锁.write {
                if (初始化完成) return@withContext
                初始化完成 = true

                规则文件列表.clear()

                // 用户自定义的
                规则文件列表.add(广告拦截订阅文件详情(""))

                for (规则 in 广告拦截订阅文件助手(读写锁).获取所有规则()){
                    if (!规则.stop) { // 只加载启用的规则
                        规则文件列表.add(广告拦截订阅文件详情(规则.name))
                    }
                }
                mlog("广告拦截初始化完成", 规则文件列表.size)
            }
        }
    }

    // -- 文件路径 --
    val 文件夹路径 = 应用配置.路径_广告过滤规则
    val 配置文件路径 = "${文件夹路径}rules.json"
    val 自定义规则路径 = 文件夹路径 + "custom.txt"
    
    // 读写锁，确保线程安全
    val 读写锁 = ReentrantReadWriteLock()

    suspend fun 添加自定义规则(规则 : String) : Boolean{
        if (!初始化完成) return false
        读写锁.write {
            规则文件列表[0].添加规则(规则) // 0 就是自定义规则文件，详见初始化
        }
        return true
    }

    fun 读取规则文件详情(文件名称 : String) : 广告拦截订阅文件详情?{
        return 读写锁.read {
            for (f in 规则文件列表) {
                if (f.文件名称 == 文件名称) return f
            }
            null
        }
    }

    fun 读取规则文件下规则项目列表(文件名称 : String) : MutableList<广告拦截规则信息>{
        val list : MutableList<广告拦截规则信息> = ArrayList()
        val adFile = 读取规则文件详情(文件名称) ?: return list
        list.addAll(adFile.拦截地址)
        list.addAll(adFile.白名单地址)
        list.addAll(adFile.拦截元素)
        list.addAll(adFile.白名单元素)
        return list
    }


    /**
     * 匹配
     */
    fun 匹配(文件地址 : String, 所在网页地址:String) : 广告拦截规则信息?{
        // 不等待不并行
        if (读写锁.isWriteLocked) return null

        val 拦截地址 = 拦截地址信息(文件地址)
        if (拦截地址.keys.size == 0 || 所在网页地址 == "") return null

        val 网页信息 = 请求的网页信息(所在网页地址)

        读写锁.read {
            for (ad in 规则文件列表){
                // 匹配白名单
                if (匹配(拦截地址,网页信息, ad.白名单地址) != null)  return null

                匹配(拦截地址,网页信息, ad.拦截地址)?.let {
                    return it
                }
            }
        }
        return null
    }
    private fun 匹配(拦截地址 : 拦截地址信息, 网页信息 : 请求的网页信息, ruleList: MutableList<广告拦截规则信息>) : 广告拦截规则信息?{

        for (key in 拦截地址.keys) {
            for (r in 匹配地址(ruleList, key)) if (规则比对(r,拦截地址,网页信息)) return r
        }

        // 无序
        for (r in 匹配地址(ruleList, 0L)) if (规则比对(r,拦截地址,网页信息)) return r

        return null
    }
    private fun 匹配地址(list : List<广告拦截规则信息>, target: Long) : MutableList<广告拦截规则信息> {
        val result : MutableList<广告拦截规则信息> = ArrayList()

        // 获取无序
        if (target == 0L){
            for (a in list) {
                if (a.k == 0L){
                    result.add(a)
                }else{
                    return result
                }
            }
            return result
        }

        var left = 0
        var right = list.size - 1
        if (list.isEmpty() || target > list[right].k) return result
        while(left <= right){
            val mid : Int = (left+right) / 2
            if (target == list[mid].k){
                var id = mid
                // 向前读取
                while (--id >=0 && list[id].k == target)
                    result.add(list[id])
                // 向后读取
                id = mid
                while (++id < list.size && list[id].k == target) result.add(list[id])
                result.add(list[mid])

                break
            }else if (list[mid].k < target){
                left = mid + 1
            }else if (list[mid].k > target){
                right = mid - 1
            }
        }
        return result
    }
    private fun 规则比对(拦截规则 : 广告拦截规则信息, 拦截地址 : 拦截地址信息, 网页信息 : 请求的网页信息) : Boolean{

        // 进阶语法匹配
        if (拦截规则.g != ""){
            for (i in 拦截规则.g.indices) {
                when(拦截规则.g.substring(i,i+1)){
                    Adblock规则解析操作.TYPE_S_IMAGE -> if (拦截地址.ftype != Adblock规则解析操作.TYPE_S_IMAGE) return false
                    Adblock规则解析操作.TYPE_S_IMAGE_F -> if (拦截地址.ftype == Adblock规则解析操作.TYPE_S_IMAGE) return false
                    Adblock规则解析操作.TYPE_S_SCRIPT -> if (拦截地址.ftype != Adblock规则解析操作.TYPE_S_SCRIPT) return false
                    Adblock规则解析操作.TYPE_S_SCRIPT_F -> if (拦截地址.ftype == Adblock规则解析操作.TYPE_S_SCRIPT) return false
                    Adblock规则解析操作.TYPE_S_STYLESHEET -> if (拦截地址.ftype != Adblock规则解析操作.TYPE_S_STYLESHEET) return false
                    Adblock规则解析操作.TYPE_S_STYLESHEET_F -> if (拦截地址.ftype == Adblock规则解析操作.TYPE_S_STYLESHEET) return false
                    Adblock规则解析操作.TYPE_S_MEDIA -> if (拦截地址.ftype != Adblock规则解析操作.TYPE_S_MEDIA) return false
                    Adblock规则解析操作.TYPE_S_MEDIA_F -> if (拦截地址.ftype == Adblock规则解析操作.TYPE_S_MEDIA) return false
                    Adblock规则解析操作.TYPE_S_OBJECT -> if (拦截地址.ftype != Adblock规则解析操作.TYPE_S_OBJECT) return false
                    Adblock规则解析操作.TYPE_S_OBJECT_F -> if (拦截地址.ftype == Adblock规则解析操作.TYPE_S_OBJECT) return false
                    Adblock规则解析操作.TYPE_S_THIRD -> if (拦截地址.host == 网页信息.主机) return false
                    Adblock规则解析操作.TYPE_S_THIRD_F -> if (拦截地址.host != 网页信息.主机) return false
                }
            }
        }

        // $domain
        if (拦截规则.gn != ""){
            var eq = false // 是否匹配

            for (s in ( 拦截规则.gn + "|").split("|")) {
                if (s.isEmpty()) continue

                // 123.com^
                var gnv = s
                if (gnv.endsWith("^")) gnv = s.substring(0,s.length - 1)

                // ~ 反向识别
                val f = gnv.startsWith("~")
                if (f) gnv = gnv.substring(1)

                if (网页信息.主机.endsWith(gnv)
                    && (gnv.length == 网页信息.主机.length
                            || (网页信息.主机.left(gnv)?:".").endsWith("."))) {

                    // 反匹配通过 = 不通过匹配 直接KO
                    if (f){
                        return false
                    }else{
                        eq = true
                        break
                    }
                }else if (f){
                    // 反 不通过 = 通过匹配
                    eq = true
                }
            }


            if (!eq) return false
        }

        // 正则表达式
        if (拦截规则.r.startsWith("/") && 拦截规则.r.endsWith("/")){
            return 文本操作.正则匹配(拦截地址.url,拦截规则.r.substring(1,拦截规则.r.length - 1))
        }

        // 尾部单管线，结束符
        var r = 拦截规则.r
        if (r.endsWith("|")) r= r.substring(0,r.length - 1)

        var b = false
        if (r.startsWith("||")){
            // 双管线 匹配地址开头
            r = r.substring(2)

            // 获取规则的完整域名
            var domain = r.left(":") ?: r.left("/")
            if (domain == null || domain.contains("^")){
                domain = r.left("^") ?: r
            }

            // 匹配域名  ||e. abe.com   e.abc.com  ，需要判断匹配的是否为完整的子域名，不能使用正则
            var index = 拦截地址.host.indexOf(domain)
            while (index != -1){

                // e.abc.com  √
                if (index == 0) break

                // abc.e.com
                else if (拦截地址.host.substring(index - 1,index) == ".") break

                else index = 拦截地址.host.indexOf(domain,index+1)
            }

            if (index != -1){
                // 组装起始位置

                val q = (拦截地址.url.right("://")?:拦截地址.url).substring(index)
                b = 简易文本匹配器.匹配(q,r,true)
            }else{
                return false
            }


        }
        else if (r.startsWith("|")){
            b =  简易文本匹配器.匹配(拦截地址.url ,r.substring(1),true)
        }
        else if (r.startsWith("http")){
            b = 简易文本匹配器.匹配(拦截地址.url,r,true)
        }
        else{
            b = 简易文本匹配器.匹配(拦截地址.url,r,false)
        }


        // 匹配结束符
        if (b){
            if (拦截规则.r.endsWith("|")){
                val r2 = 拦截规则.r.substring(0,拦截规则.r.length - 1)

                var t = r2.right2("*")
                if (t == null || t.contains("^")){
                    t = r2.right2("^") ?: r2
                }
                return 拦截地址.url.endsWith(t)
            }else{
                return true
            }
        }

        return false
    }
    private fun 匹配多个(拦截地址 : 拦截地址信息, 网页信息 : 请求的网页信息, ruleList: MutableList<广告拦截规则信息>) : List<广告拦截规则信息>{
        读写锁.read {
            val ls = ArrayList<广告拦截规则信息>()

            for (key in 拦截地址.keys) {
                for (r in 匹配地址(ruleList, key)) if (规则比对(r,拦截地址,网页信息)) {
                    ls.add(r)
                }
            }

            // 无序
            for (r in 匹配地址(ruleList, 0L)) if (规则比对(r,拦截地址,网页信息)) {
                ls.add(r)
            }

            return ls
        }
    }

    fun 获取拦截元素列表(所在网页地址 : String) : List<广告拦截规则信息>? {
        if (读写锁.isWriteLocked) return null
        val 网页信息 = 请求的网页信息(所在网页地址)

        // 拦截元素的地址绑定网页地址而不是资源文件地址  so 这里正确的
        val 拦截地址 = 拦截地址信息(所在网页地址)
        if (拦截地址.keys.size == 0 || 所在网页地址 == "") return null

        读写锁.read {
            val result : MutableList<广告拦截规则信息> = ArrayList()
            for (ad in 规则文件列表) {
                result.addAll(匹配多个(拦截地址,网页信息, ad.拦截元素))
            }
            return result
        }

    }


    // -- 规则解析 --
    fun 解析(rule : String, listener : (广告拦截规则信息)->Unit){

        try {
            // 排除注释
            if (rule.length < 3 || rule.startsWith("!") || rule.startsWith("！") || rule.startsWith("~")) return

            var ad = 广告拦截规则信息()
            ad.a = rule
            ad.r = rule

            // 分离 元素隐藏值
            if (rule.contains("#@#")){
                ad.w = Adblock规则解析操作.白名单元素
                ad.e = rule.right("#@#")?:return
                ad.r = rule.left("#@#")?:""
            }
            else if (rule.contains("##")){
                ad.w = Adblock规则解析操作.隐藏元素
                ad.e = rule.right("##")?:return
                ad.r = rule.left("##")?:""
            }
            else if (rule.contains("$$")){
                ad.w = Adblock规则解析操作.隐藏元素
                ad.e = rule.right("$$")?:return
                ad.r = rule.left("$$")?:""
            }
            else if (rule.contains("#?#")){
                // 不搞
                return
            }

            // 单纯的广告标识 ##xxxx
            if (ad.r.isEmpty() && ad.e.isNotEmpty()){
                listener(ad)
                return
            }


            // 分离 白名单
            if (ad.r.startsWith("@@")){
                ad.w = Adblock规则解析操作.白名单地址
                ad.r = ad.r.substring(2)
            }

            // 分离 进阶语法
            if (ad.r.indexOf("$")!=-1){
                val orule = ad.r
                ad.r = orule.left("$")?:return
                ad = 解析进阶语法(ad, orule.right("$")?:return) ?: return
            }

            // 无序全适配
            if (ad.r == ""){
                ad.k = 0
            }

            // 地址用于多个域名 逗号分割，这个规则不允许有通配符
            else if (ad.r.contains(",")
                && !ad.r.contains("/") && !ad.r.contains("*") && !ad.r.contains(";")
                && !ad.r.contains("&") && !ad.r.contains("(") && !ad.r.contains("^")){

                for (s in ad.r.split(",")) {
                    if (s.isEmpty()) continue
                    val newAd = 克隆(ad)
                    newAd.k = Adblock规则解析操作.getYuMingKey(s)
                    newAd.r = s
                    listener.invoke(newAd)
                }
                return
            }


            // 1 IP IP
            val ip = Adblock规则解析操作.getIp(ad.r)
            if (ip != null){
                ad.k = Adblock规则解析操作.getIpKey(ip)
                listener.invoke(ad)
                return
            }

            // 2 域名 YUMING
            val ym = Adblock规则解析操作.getYuMing(ad.r)
            if (ym != null){
                ad.k = Adblock规则解析操作.getYuMingKey(ym)
                listener.invoke(ad)
                return
            }

            // 3 端口 DUANKOU
            val dk = Adblock规则解析操作.getDuanKou(ad.r)
            if (dk != null){
                ad.k = Adblock规则解析操作.getDuanKouKey(dk).toLong()
                listener.invoke(ad)
                return
            }

            // 4 段落 JIE
            val jie = Adblock规则解析操作.getJie(ad.r)
            if (jie != null){
                ad.k = Adblock规则解析操作.getJieKey(jie).toLong()
                listener.invoke(ad)
                return
            }

            // 5 固定开始值  HTTP HTTPS HTTPX
            val jie2 = Adblock规则解析操作.getKaishiKey(ad.r)
            if (jie2 != null){
                ad.k = jie2.toLong()
                listener.invoke(ad)
                return
            }

            // 6 左斜线 /[a-z0-9-_]{5}  ZUOXIEXIAN
            val zuo = Adblock规则解析操作.getZuoXieXian(ad.r)
            if (zuo != null){
                ad.k = Adblock规则解析操作.getZuoXieXianKey(zuo).toLong()
                listener.invoke(ad)
                return
            }

            // 7 右斜线 [a-z0-9-_]{5}/  YOUXIEXIAN
            val you = Adblock规则解析操作.getYouXieXian(ad.r)
            if (you != null){
                ad.k = Adblock规则解析操作.getYouXieXianKey(you).toLong()
                listener.invoke(ad)
                return
            }

            // 8. 高级语法，反 ~hkcsl.com  ，必须为完整的域名， 改成高级语法匹配  ~zxxxx.com
            if (ad.r.startsWith("~")){
                val domain = Adblock规则解析操作.getYuMing(ad.r.substring(1))
                if (domain != null){
                    ad.k = 0
                    ad.r = ""
                    ad.gn += "~" + domain
                    listener.invoke(ad)
                }
            }

            listener.invoke(ad)
        }catch (e : Exception){
            // 失败的规则 不管了
            e.printStackTrace()
        }

    }

    // 分解进阶语法
    fun 解析进阶语法(ad: 广告拦截规则信息, grammar: String) : 广告拦截规则信息?{
        val result = StringBuilder()

        // 部分多个高级语法使用多个 $ 隔开，部分使用 , 隔开

        for (s in grammar.replace("$",",").split(",")) {
            var v = s
            var f = false
            if (s.startsWith("~")){
                v = s.substring(1)
                f = true
            }
            if (v.startsWith("domain=")){

                ad.gn = v.right("=") ?: ""
            }else{
                when(v){
                    "script","scipt"->{
                        if (f) result.append(Adblock规则解析操作.TYPE_S_SCRIPT_F)
                        else  result.append(Adblock规则解析操作.TYPE_S_SCRIPT)
                    }
                    "image" -> {
                        if (f)  result.append(Adblock规则解析操作.TYPE_S_IMAGE_F)
                        else  result.append(Adblock规则解析操作.TYPE_S_IMAGE)
                    }
                    "stylesheet" ->{
                        if (f)  result.append(Adblock规则解析操作.TYPE_S_STYLESHEET_F)
                        else  result.append(Adblock规则解析操作.TYPE_S_STYLESHEET)
                    }
                    "media" ->{
                        if (f)  result.append(Adblock规则解析操作.TYPE_S_MEDIA_F)
                        else  result.append(Adblock规则解析操作.TYPE_S_MEDIA)
                    }
                    "object" -> {
                        if (f)  result.append(Adblock规则解析操作.TYPE_S_OBJECT_F)
                        else  result.append(Adblock规则解析操作.TYPE_S_OBJECT)
                    }
                    "" -> {
                        if (f)  result.append(Adblock规则解析操作.TYPE_S_XMLHTTPREQUEST_F)
                        else  result.append(Adblock规则解析操作.TYPE_S_XMLHTTPREQUEST)
                    }
                    "third-party" -> {
                        if (f)  result.append(Adblock规则解析操作.TYPE_S_THIRD_F)
                        else  result.append(Adblock规则解析操作.TYPE_S_THIRD)
                    }
                    else -> {
                        return null
                    }
                }
            }
        }

        ad.g = result.toString()
        return ad
    }



    /**
     * 克隆一个新的adRule
     */
    fun 克隆(old : 广告拦截规则信息) : 广告拦截规则信息 {
        val newOld = 广告拦截规则信息()
        newOld.k = old.k
        newOld.r = old.r
        newOld.g = old.g
        newOld.gn = old.gn
        newOld.e = old.e
        return newOld
    }



}